package com.cloudkinto.service.storage.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cloudkinto.bo.storage.StorageAreaAddBo;
import com.cloudkinto.bo.storage.StorageAreaQueryBo;
import com.cloudkinto.bo.storage.StorageAreaUpdateBo;
import com.cloudkinto.bo.storage.StorageImportBo;
import com.cloudkinto.common.BeanConvert;
import com.cloudkinto.common.constant.SysConstant;
import com.cloudkinto.common.exception.BizException;
import com.cloudkinto.common.utils.ExcelUtils;
import com.cloudkinto.common.utils.StringsUtil;
import com.cloudkinto.common.web.PageResult;
import com.cloudkinto.common.web.SingleResult;
import com.cloudkinto.dao.ProductStockRackDao;
import com.cloudkinto.dao.StorageAreaDao;
import com.cloudkinto.dao.StorageLocationDao;
import com.cloudkinto.dto.storage.StorageAreaImportDto;
import com.cloudkinto.entity.ProductStockRackDo;
import com.cloudkinto.entity.StorageAreaDo;
import com.cloudkinto.entity.StorageLocationDo;
import com.cloudkinto.enumType.storage.StorageAreaEnum;
import com.cloudkinto.service.storage.StorageAreaService;
import com.cloudkinto.vo.common.CommonBatchOperationVo;
import com.cloudkinto.vo.storage.StorageAreaVo;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * <p>
 * 库区表 service实现类
 * </p>
 *
 * @author long
 * @since 2024-11-12
 */
@Service
@Transactional(rollbackFor = Exception.class)
@RequiredArgsConstructor
public class StorageAreaServiceImpl extends ServiceImpl<StorageAreaDao, StorageAreaDo> implements StorageAreaService {


    private final StorageLocationDao storageLocationDao;

    private final ProductStockRackDao productStockRackDao;

    /**
     * 用于stream流的filter方法做实体类的某个属性去重
     */
    static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
        Map<Object, Boolean> seen = new ConcurrentHashMap<>();
        //putIfAbsent方法添加键值对，如果map集合中没有该key对应的值，则直接添加，并返回null，如果已经存在对应的值，则依旧为原来的值。
        //如果返回null表示添加数据成功(不重复)，不重复(null==null :TRUE)
        return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
    }

    @Override
    public void add(StorageAreaAddBo req, Long tenantId) {
//        StorageAreaDo entityDo = new StorageAreaDo();
//        BeanUtils.copyProperties(req, entityDo);
        StorageAreaDo entityDo = BeanConvert.INSTANCE.storageAreaAddBoToStorageAreaDo(req);
        entityDo.setTenantId(tenantId);
        //库区编码唯一判断
        StorageAreaDo one = baseMapper.selectOne(
                Wrappers.<StorageAreaDo>lambdaQuery()
                        .eq(StorageAreaDo::getCode, req.getCode())
                        .last(" limit 1")
        );
        if (Objects.nonNull(one)) {
            throw new BizException(SysConstant.Storage_Area_Code_Exist);
        }
        baseMapper.insert(entityDo);
    }

    @Override
    public void update(StorageAreaUpdateBo req, Long userId) {
        StorageAreaDo oldDo = baseMapper.selectById(req.getId());
//        StorageAreaDo entityDo = new StorageAreaDo();
//        BeanUtils.copyProperties(req, entityDo);
        StorageAreaDo entityDo = BeanConvert.INSTANCE.storageAreaUpdateBoToStorageAreaDo(req);
        baseMapper.updateById(entityDo);
        //更新库区下库位库存属性
        if (!oldDo.getMainType().equals(entityDo.getMainType())) {
            //查询库区下的库位id
            List<Long> cellIds = storageLocationDao.selectList(
                    Wrappers.<StorageLocationDo>lambdaQuery()
                            .eq(StorageLocationDo::getAreaId, oldDo.getId())
            ).stream().map(StorageLocationDo::getId).collect(Collectors.toList());
            ProductStockRackDo productStockRackDo = new ProductStockRackDo();
            productStockRackDo.setStockAttribute(entityDo.getMainType() ? 1 : 0);
            productStockRackDao.update(
                    productStockRackDo,
                    Wrappers.<ProductStockRackDo>lambdaQuery()
                            .in(ProductStockRackDo::getRackId, cellIds)
            );
        }
    }

    @Override
    public int delete(Long id, Long userId) {
        return baseMapper.deleteById(id);
    }

    @Override
    public StorageAreaDo detail(Long id) {
        return baseMapper.selectById(id);
    }

    @Override
    public SingleResult<Map<String, Object>> pageInit() {
        HashMap<String, Object> map = new HashMap<>(2);
        map.put("storageAreaType", StorageAreaEnum.getMapList());
        return SingleResult.success(map);
    }

    @Override
    public PageResult<StorageAreaVo> getListPage(StorageAreaQueryBo bo) {
        Integer current = bo.getCurrent();
        Integer pageSize = bo.getPageSize();
        PageHelper.startPage(current, pageSize);
        List<StorageAreaVo> list = baseMapper.getListPage(bo);
        PageInfo<StorageAreaVo> page = new PageInfo<>(list);
        return new PageResult<>(list, current, pageSize, (int) page.getTotal(), page.getPages());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public CommonBatchOperationVo importExcel(StorageImportBo bo, Long tenantId) throws BizException {
        CommonBatchOperationVo res = new CommonBatchOperationVo();
        List<StorageAreaImportDto> importDtoList;
        try {
            importDtoList = ExcelUtils.importExcel(bo.getFile(), 1, 0, StorageAreaImportDto.class);
        } catch (Exception e) {
            throw new BizException(SysConstant.Excel_Template_Error);
        }
        int oldSize = importDtoList.size();
        res.setTotal(importDtoList.size());
        //根据库区编码去重
        importDtoList = importDtoList.stream().filter(
                        i -> StrUtil.isNotBlank(i.getCode())
                                && Objects.nonNull(i.getType())
                                && StrUtil.isNotBlank(i.getName())
                )
                .collect(Collectors.toList());
        int newSize = importDtoList.size();
        if (oldSize != newSize) {
            HashMap<String, Object> map = new HashMap<>(4);
            map.put("code", "Exception");
            map.put("errorMsg", StringsUtil.createI18Message(SysConstant.Import_Required_Field_Blank));
            res.setFailed(res.getFailed() + oldSize - newSize);
            res.getFailedList().add(map);
        }
        if (importDtoList.isEmpty()) {
            return res;
        }
        importDtoList = importDtoList.stream().filter(distinctByKey(StorageAreaImportDto::getCode)).collect(Collectors.toList());
        Map<String, StorageAreaDo> codeMatch = baseMapper.selectList(Wrappers.<StorageAreaDo>lambdaQuery()
                .eq(StorageAreaDo::getTenantId, tenantId)
                .eq(StorageAreaDo::getWhId, bo.getWhId())
                .in(StorageAreaDo::getCode, importDtoList.stream().map(StorageAreaImportDto::getCode).collect(Collectors.toList()))
        ).stream().collect(Collectors.toMap(StorageAreaDo::getCode, val -> val));
        for (StorageAreaImportDto importDto : importDtoList) {
//            StorageAreaDo storageAreaDo = new StorageAreaDo();
//            BeanUtils.copyProperties(importDto, storageAreaDo);
            StorageAreaDo storageAreaDo = BeanConvert.INSTANCE.storageAreaImportDtoToStorageAreaDo(importDto);
            storageAreaDo.setWhId(bo.getWhId());
            storageAreaDo.setTenantId(tenantId);
            StorageAreaEnum areaEnum = StorageAreaEnum.getAreaEnum(importDto.getType());
            StorageAreaDo areaDo = codeMatch.get(storageAreaDo.getCode());
            if (Objects.nonNull(areaDo)) {
                HashMap<String, Object> map = new HashMap<>(4);
                map.put("code", storageAreaDo.getCode());
                map.put("errorMsg", StringsUtil.createI18Message(SysConstant.Area_Exist));
                res.setFailed(res.getFailed() + 1);
                res.getFailedList().add(map);
            } else if (Objects.isNull(areaEnum)) {
                HashMap<String, Object> map = new HashMap<>(4);
                map.put("code", storageAreaDo.getCode());
                map.put("errorMsg", "Zone Type/库区类型 Wrong,Please check!");
                res.setFailed(res.getFailed() + 1);
                res.getFailedList().add(map);
            } else {
                storageAreaDo.setMainType(areaEnum.getMainType());
                baseMapper.insert(storageAreaDo);
                res.setSuccess(res.getSuccess() + 1);
            }
        }
        return res;
    }
}
